#include "psync_internal.hpp"

using namespace Psync5;

int psync_core_node_tag;

LUAF(create_node)
{
	void* mem = lua_newuserdata(L, sizeof(LuaNode));
	LuaNode* self = new (mem) LuaNode();
	prepare_object(L, &psync_core_node_tag); // self -- self

	return 1;
}

LUAF(node_host)
{
	auto& node = check_node(L, 1);

	int port = luaL_checkint(L, 2);
	node.host(port);

	return 1;
}

LUAF(node_connect)
{
	auto& node = check_node(L, 1);

	char const* addr = luaL_checkstring(L, 2);
	int port = luaL_checkint(L, 3);

	lua_pushinteger(L, node.connect(addr, port)->id);

	return 1;
}

LUAF(node_disconnect)
{
	auto& node = check_node(L, 1);

	for(auto r = node.allLinks(); !r.empty(); r.pop_front())
	{
		r.front().disconnect();
	}

	return 0;
}

LUAF(node_link_latency)
{
	auto& node = check_node(L, 1);

	Link* link = node.linkFromId(luaL_checkint(L, 2));
	if(!link)
		luaL_error(L, "invalid link");

	lua_pushnumber(L, link->calcLatency());
	return 1;
}

LUAF(node_set_accept_connections)
{
	auto& node = check_node(L, 1);

	bool accept = lua_toboolean(L, 2) != 0;

	node.acceptConnections(accept);
	return 0;
}

LUAF(node_gc)
{
	auto& node = check_node(L, 1);

	node.~LuaNode();
	return 0;
}

#if 0
LUAF(node_put_section)
{
	auto& node = check_node(L, 1);

	Link* link = node.linkFromId(luaL_checkint(L, 2));

	if(!link)
		luaL_error(L, "invalid link");

	Channel* channel = link->channel(luaL_checkint(L, 3));

	if(!channel)
		luaL_error(L, "invalid channel");

	if(!link->allowsSending())
		return 0; // Ignore

	unsigned dataSize = (unsigned)luaL_checkint(L, 3);

	int wordCount = (dataSize + 31) / 32;

	if(wordCount > Section::MaxBufSize)
		luaL_error(L, "too large section");

	auto& section = channel->newSendSection();

	luaL_checktype(L, 5, LUA_TTABLE);

	double* data = reinterpret_cast<double*>(luaext_rawarraybase(L, 5, wordCount));

	for(int i = 0; i < wordCount; ++i)
	{
		section.buffer[i] = int(data[i]);
	}

	section.size = dataSize;

	return 0;
}
#endif

LUAF(node_extract_serializer)
{
	auto& node = check_node(L, 1);

	LuaLink* link = static_cast<LuaLink*>(node.linkFromId(luaL_checkint(L, 2)));

	if(!link)
		luaL_error(L, "invalid link");

	if(link->serialize)
		luaL_error(L, "serializer was already extracted");

	link->createContext(L);
	return 1;
}

LUAF(node_process)
{
	auto& node = check_node(L, 1);

	node.process();

	return 0;
}

#if 0
LUAF(node_send_packet_all)
{
	auto& node = check_node(L, 1);

	for(auto r = node.allLinks(); !r.empty(); r.pop_front())
	{
		r.front().assembleAndSendPacket();
	}

	return 0;
}
#endif

LUAF(node_link_send_packet)
{
	auto& node = check_node(L, 1);

	Link* link = node.linkFromId(luaL_checkint(L, 2));
	if(!link) luaL_error(L, "invalid link");

	link->assembleAndSendPacket();

	return 0;
}

LUAF(node_link_time_for_packet)
{
	auto& node = check_node(L, 1);

	Link* link = node.linkFromId(luaL_checkint(L, 2));
	if(!link) luaL_error(L, "invalid link");

	lua_pushboolean(L, link->isTimeForPacket());

	return 1;
}

#if 0
LUAF(node_next_section)
{
	auto& node = check_node(L, 1);

	Link* link = node.linkFromId(luaL_checkint(L, 2));

	if(!link)
		luaL_error(L, "invalid link");

	Channel* channel = link->channel(luaL_checkint(L, 3));

	if(!channel)
		luaL_error(L, "invalid channel");

	std::unique_ptr<Section> section(channel->recvQueue.nextSection());

	if(section)
	{
		int wordCount = (section->size + 31) / 32;

		luaL_checktype(L, 4, LUA_TTABLE);

		double* p = reinterpret_cast<double*>(luaext_rawarraybase(L, 4, wordCount));
		uint32_t const* buf = section->buffer;

		for(int i = 0; i < wordCount; ++i)
			p[i] = buf[i];

		// '- 1' is omitted because the indices start at 1, not 0
		lua_pushinteger(L, ((section->size + 31) / 32));
		lua_pushinteger(L, ((section->size + 31) % 32) + 1);
	}
	else
	{
		lua_pushinteger(L, 0);
		lua_pushinteger(L, 0);
	}

	return 2;
}
#endif

LUAF(node_next_event)
{
	auto& node = check_node(L, 1);

	if(node.events.empty())
		return 0;

	LuaNodeEvent& ev = node.events.front();

	if(ev.type == LuaNodeEvent::Connect)
	{
		lua_pushstring(L, "link connect");
		lua_pushinteger(L, ev.linkId);
	}
	else if(ev.type == LuaNodeEvent::Disconnect)
	{
		lua_pushstring(L, "link disconnect");
		lua_pushinteger(L, ev.linkId);
	}
	else if(ev.type == LuaNodeEvent::LinkCreated)
	{
		lua_pushstring(L, "link created");
		lua_pushinteger(L, ev.linkId);
	}
	node.events.pop();
	return 2;
}

extern "C" static void overflow(bytewriter* w, int b)
{
	LuaLink* link = static_cast<LuaLink*>(w->user);

	if(link->nextSection)
	{
		// Correct size of section
		link->nextSection->size = int(w->next - reinterpret_cast<unsigned char const*>(link->nextSection->buffer)) * 8;

		if(link->nextSection->size != 0)
		{
			// Queue nextSection
			link->channel().queue(link->nextSection.release());
		}
	}

	if(!link->nextSection || link->nextSection->size > 0)
	{
		// Prepare a new section
		link->nextSection.reset(link->channel().newSendSection());

		w->next = reinterpret_cast<unsigned char*>(link->nextSection->buffer);
		w->end = reinterpret_cast<unsigned char*>(link->nextSection->buffer + Section::MaxBufSize);
	}

	if(b >= 0)
		*w->next++ = b;
}

extern "C" static int underflow(bytereader* r)
{
	LuaLink* link = static_cast<LuaLink*>(r->user);

	Psync5::Channel& channel = link->channel();
	link->curRecvSection = channel.recvQueue.nextSection();

	if(link->curRecvSection)
	{
		int byteSize = (link->curRecvSection->size + 7) / 8;;
		r->next = reinterpret_cast<unsigned char const*>(link->curRecvSection->buffer);
		r->end = reinterpret_cast<unsigned char const*>(link->curRecvSection->buffer) + byteSize;
		return 1;
	}

	return 0;
}

extern "C" static void writer_destroy(bytewriter* w)
{
	LuaLink* link = static_cast<LuaLink*>(w->user);

	// Sever tie
	link->serialize = 0;
}

extern "C" static void reader_destroy(bytereader* r)
{
	LuaLink* link = static_cast<LuaLink*>(r->user);

	// Sever tie
	link->serialize = 0;
}

void LuaLink::createContext(lua_State* L)
{
	serialize_context* ctx = serialize_create(L);

	ctx->writer.overflow = overflow;
	ctx->writer.destroy = writer_destroy;
	ctx->writer.user = this;

	ctx->reader.underflow = underflow;
	ctx->reader.destroy = reader_destroy;
	ctx->reader.user = this;

	serialize = ctx;
}